﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using OverStore.Configuration.ComponentModel;
using OverStore.Configuration.PersistenceServiceProvider;
using OverStore.Configuration.Validation;
using OverStore.Exceptions;
using OverStore.Exceptions.Configuration;
using OverStore.Runtime;
using OverStore.Runtime.Logger;
using OverStore.Runtime.OverStoreContext;
using OverStore.Runtime.Storage;
using OverStore.Runtime.ObjectRepository;

namespace OverStore.Configuration
{
    /// <summary>
    /// Contains configuration options for creating instances of <see cref="IPersistenceSession"/>.
    /// <seealso cref="OverStorePersistenceSession"/>
    /// </summary>
    public sealed class OverStoreConfiguration : ISupportValidation, IPersistenceSessionFactory
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="OverStoreConfiguration"/> class.
        /// </summary>
        public OverStoreConfiguration()
        {
            Logger = SingletonConfiguration<ILogger, ConsoleLogger>.Value;
            Repositories = new Collection<IComponentConfiguration<IRepositoryBase>>();
            PersistenceServiceProvider = new DefaultPersistenceServiceProviderConfiguration();
            ValidateOnCreateSession = true;
        }

        /// <summary>
        /// Gets or sets the data storage used for persist object data.
        /// </summary>
        public IComponentConfiguration<IDataStorage> DataStorage { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="IComponentConfiguration{ILogger}"/> allows to output diagnostics information.
        /// By default, <see cref="ConsoleLogger"/> is used.
        /// </summary>
        public IComponentConfiguration<ILogger> Logger { get; set; }

        /// <summary>
        /// Gets or sets the persistence service provider.
        /// By default, the <see cref="DefaultPersistenceServiceProviderConfiguration"/> used, 
        /// and in most cases you should not change this result.
        /// </summary>
        public IPersistenceServiceProviderConfiguration PersistenceServiceProvider { get; set; }

        /// <summary>
        /// Gets or sets a result indicating whether current instance will perform validation 
        /// of its configuration before each creating of <see cref="IPersistenceSession"/> instance.
        /// </summary>
        public bool ValidateOnCreateSession { get; set; }

        /// <summary>
        /// Gets or sets collection of the repositories allows to persists instances of different types.
        /// </summary>
        public IEnumerable<IComponentConfiguration<IRepositoryBase>> Repositories { get; set; }

        /// <summary>
        /// Adds new item to repository collection.
        /// </summary>
        public void AddRepository(IComponentConfiguration<IRepositoryBase> repository)
        {
            if (Object.ReferenceEquals(repository, null))
                throw new ArgumentNullException("repository");
            Repositories = (Repositories ?? Enumerable.Empty<IComponentConfiguration<IRepositoryBase>>()).Concat(new[] { repository }).ToList();
        }

        /// <summary>
        /// Creates and returns new instance of <see cref="IPersistenceSession"/>.
        /// </summary>
        /// <returns>
        /// Properly initialized <see cref="IPersistenceSession"/> with specified configuration.
        /// </returns>
        /// <exception cref="ConfigurationException{OverStoreConfiguration}">Error occured during creating instance of <see cref="IPersistenceSession"/>.</exception>
        public IPersistenceSession CreateSession()
        {
            if (ValidateOnCreateSession)
            {
                var vr = Validate();
                if (!vr.IsValid)
                    throw this.ConfigurationException("Configuration is not valid.");
            }

            if (DataStorage == null)
                throw this.ConfigurationException("Data Source is missing.");
            if (PersistenceServiceProvider == null)
                throw this.ConfigurationException("Persistence Service Provider is missing.");
            if (Repositories == null)
                throw this.ConfigurationException("Repositories are missing.");
            if (Repositories.Count() == 0)
                throw this.ConfigurationException("No Repositories specified.");
            var context = new OverStoreContextImplementation();

            if (Logger != null)
            {
                var logger = Logger.CreateComponent(context);
                context.Logger = logger;
            }

            var dataSource = DataStorage.CreateComponent(context);
            context.DataStorage = dataSource;

            PersistenceServiceProvider.SetupContext(context, Repositories);
            var session = new OverStorePersistenceSession(context);
            return session;
        }

        /// <summary>
        /// Perform component validation. Method should validate only
        /// component configuration itself, not sub components.
        /// If configuration is valid, null result should returns.
        /// </summary>
        /// <returns>
        ///     <see cref="T:OverStore.Configuration.ValidationResults"/> describes errors found in component configuration.
        /// </returns>
        public ValidationResult Validate()
        {
            var result = new ValidationResult(this)
                .AddError(DataStorage == null, "DataSource is null")
                .ValidateToNestedResult(DataStorage);
            foreach (var r in Repositories)
                result.ValidateToNestedResult(r);
            return result;
        }
    }
}